
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <assert.h>
#include "../ijksdl_inc_internal.h"
#include "../ijksdl_audio.h"
#include "linux_alsa.h"

#define PCM_DEVICE "default"

typedef struct AudioChannelMapEntry {
    Uint8 sdl_channel;
    int android_channel;
    const char *sdl_name;
    const char *android_name;
} AudioChannelMapEntry;
static AudioChannelMapEntry g_audio_channel_map[] = {
    { 2, CHANNEL_OUT_STEREO, "2-chan", "CHANNEL_OUT_STEREO" },
    { 1, CHANNEL_OUT_MONO, "1-chan", "CHANNEL_OUT_MONO" },
};

typedef struct AudioFormatMapEntry {
    SDL_AudioFormat sdl_format;
    int android_format;
    const char *sdl_name;
    const char *android_name;
} AudioFormatMapEntry;
static AudioFormatMapEntry g_audio_format_map[] = {
    { AUDIO_S16SYS, ENCODING_PCM_16BIT, "AUDIO_S16SYS", "ENCODING_PCM_16BIT" },
    { AUDIO_U8, ENCODING_PCM_8BIT, "AUDIO_U8", "ENCODING_PCM_8BIT" },
};

static Uint8 find_sdl_channel(int android_channel)
{
    for (int i = 0; i < NELEM(g_audio_channel_map); ++i) {
        AudioChannelMapEntry *entry = &g_audio_channel_map[i];
        if (entry->android_channel == android_channel)
            return entry->sdl_channel;
    }
    return 0;
}

static int find_android_channel(int sdl_channel)
{
    for (int i = 0; i < NELEM(g_audio_channel_map); ++i) {
        AudioChannelMapEntry *entry = &g_audio_channel_map[i];
        if (entry->sdl_channel == sdl_channel)
            return entry->android_channel;
    }
    return CHANNEL_OUT_INVALID;
}

static SDL_AudioFormat find_sdl_format(int android_format)
{
    for (int i = 0; i < NELEM(g_audio_format_map); ++i) {
        AudioFormatMapEntry *entry = &g_audio_format_map[i];
        if (entry->android_format == android_format)
            return entry->sdl_format;
    }
    return AUDIO_INVALID;
}

static const char * find_sdl_name(int android_format)
{
    for (int i = 0; i < NELEM(g_audio_format_map); ++i) {
        AudioFormatMapEntry *entry = &g_audio_format_map[i];
        if (entry->android_format == android_format)
            return entry->sdl_name;
    }
    return "";
}

static int find_android_format(int sdl_format)
{
    for (int i = 0; i < NELEM(g_audio_format_map); ++i) {
        AudioFormatMapEntry *entry = &g_audio_format_map[i];
        if (entry->sdl_format == sdl_format)
            return entry->android_format;
    }
    return ENCODING_INVALID;
}

static snd_pcm_t *SDL_Linux_Alsa_open(uint32_t sample_rate, uint32_t channels, int bits_per_sample, snd_pcm_uframes_t frames) {
    int pcm;
    snd_pcm_t *pcm_handle;
    snd_pcm_hw_params_t *params;

    printf("采样率: %u, 通道数: %u, 位深: %u\n", sample_rate, channels, bits_per_sample);

    pcm = snd_pcm_open(&pcm_handle, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0);
    if (pcm < 0) {
        printf("无法打开PCM设备: %s\n", snd_strerror(pcm));
        return 0;
    }

    snd_pcm_hw_params_alloca(&params);
    snd_pcm_hw_params_any(pcm_handle, params);
    snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);

    // 设置采样格式
    snd_pcm_format_t format;
    if (bits_per_sample == 16) {
        format = SND_PCM_FORMAT_S16_LE;
    } else if (bits_per_sample == 8) {
        format = SND_PCM_FORMAT_U8;
    } else {
        printf("不支持的位深: %u\n", bits_per_sample);
        snd_pcm_close(pcm_handle);
        return 0;
    }
    snd_pcm_hw_params_set_format(pcm_handle, params, format);

    snd_pcm_hw_params_set_channels(pcm_handle, params, channels);
    snd_pcm_hw_params_set_rate_near(pcm_handle, params, &sample_rate, 0);
    snd_pcm_hw_params_set_period_size_near(pcm_handle, params, &frames, 0);

    pcm = snd_pcm_hw_params(pcm_handle, params);
    if (pcm < 0) {
        printf("无法设置硬件参数: %s\n", snd_strerror(pcm));
        snd_pcm_close(pcm_handle);
        return 0;
    }

    return pcm_handle;
}

typedef struct SDL_Linux_Alsa {
    snd_pcm_t *handle;
    SDL_Linux_Alsa_Spec spec;
    FILE *file;
} SDL_Linux_Alsa;


static void SDL_Linux_Alsa_get_default_spec(SDL_Linux_Alsa_Spec *spec)
{
    assert(spec);
    spec->stream_type = STREAM_MUSIC;
    spec->sample_rate_in_hz = 0;
    spec->channel_config = CHANNEL_OUT_STEREO;
    spec->audio_format = ENCODING_PCM_16BIT;
    spec->buffer_size_in_bytes = 0;
    spec->mode = MODE_STREAM;
}

SDL_Linux_Alsa *SDL_Linux_Alsa_new_from_spec(SDL_Linux_Alsa_Spec *spec)
{
    SDL_Linux_Alsa *alsa = (SDL_Linux_Alsa*) mallocz(sizeof(SDL_Linux_Alsa));
    if (!alsa) {
        return NULL;
    }

    alsa->spec = *spec;
    alsa->spec.buffer_size_in_bytes = 8192;

    if (alsa->spec.sample_rate_in_hz < 4000 || alsa->spec.sample_rate_in_hz > 48000) {
        int native_sample_rate_in_hz = 16000;
        if (native_sample_rate_in_hz > 0) {
            ALOGE("SDL_Linux_Alsa_new: cast sample rate %d to %d:",
                alsa->spec.sample_rate_in_hz,
                native_sample_rate_in_hz);
            //alsa->spec.sample_rate_in_hz = native_sample_rate_in_hz;
        }
    }

    alsa->handle = SDL_Linux_Alsa_open(alsa->spec.sample_rate_in_hz
                    , find_sdl_channel(alsa->spec.channel_config)
                    , alsa->spec.audio_format == ENCODING_PCM_16BIT ? 16 : 8
                    , SDL_Linux_Alsa_get_min_buffer_size(alsa));
    if (!alsa->handle) {
        free(alsa);
        return 0;
    }
    //alsa->file   = fopen("test.pcm", "wb");

    return alsa;
}

SDL_Linux_Alsa *SDL_Linux_Alsa_new_from_sdl_spec(const SDL_AudioSpec *sdl_spec)
{
    SDL_Linux_Alsa_Spec alsa_spec;

    SDL_Linux_Alsa_get_default_spec(&alsa_spec);

    alsa_spec.sample_rate_in_hz = sdl_spec->freq;
    alsa_spec.channel_config = find_android_channel(sdl_spec->channels);
    alsa_spec.audio_format = find_android_format(sdl_spec->format);
    alsa_spec.buffer_size_in_bytes = sdl_spec->size;

    return SDL_Linux_Alsa_new_from_spec(&alsa_spec);
}

void SDL_Linux_Alsa_free(SDL_Linux_Alsa *alsa)
{
    if (alsa->handle) snd_pcm_close(alsa->handle);
    if (alsa->file) fclose(alsa->file);
    free(alsa);
}

int SDL_Linux_Alsa_get_min_buffer_size(SDL_Linux_Alsa *alsa)
{
    return 1024;
}

void SDL_Linux_Alsa_get_target_spec(SDL_Linux_Alsa *alsa, SDL_AudioSpec *sdl_spec)
{
    SDL_Linux_Alsa_Spec *alsa_spec = &alsa->spec;

    sdl_spec->freq = alsa_spec->sample_rate_in_hz;
    sdl_spec->channels = find_sdl_channel(alsa_spec->channel_config);
    sdl_spec->format = find_sdl_format(alsa_spec->audio_format);
    sdl_spec->size = alsa_spec->buffer_size_in_bytes;
    sdl_spec->silence = 0;
    sdl_spec->padding = 0;
}

void SDL_Linux_Alsa_play(SDL_Linux_Alsa *alsa) {
    if (alsa->handle) snd_pcm_pause(alsa->handle, 0);
}

void SDL_Linux_Alsa_pause(SDL_Linux_Alsa *alsa) {
    if (alsa->handle) snd_pcm_pause(alsa->handle, 1);
}

void SDL_Linux_Alsa_flush(SDL_Linux_Alsa *alsa)
{
}

int SDL_Linux_Alsa_write_byte(SDL_Linux_Alsa *alsa, uint8_t *data, int len) {
    if (alsa->file) fwrite(data, 1, len, alsa->file);
#if 0
    return len;
#else
    int frame_bytes = find_sdl_channel(alsa->spec.channel_config) *
                      (alsa->spec.audio_format == AUDIO_S16SYS ? 2 : 1);
    if (len < frame_bytes || (len % frame_bytes) != 0)
        return 0;
    int frames_to_write = len / frame_bytes;
    int written = 0;
    while (written < frames_to_write) {
        int ret = snd_pcm_writei(alsa->handle, data + written * frame_bytes, frames_to_write - written);
        if (ret == -EPIPE) {
            snd_pcm_prepare(alsa->handle);
            continue;
        } else if (ret < 0) {
            printf("写入错误: %s\n", snd_strerror(ret));
            break;
        }
        written += ret;
    }
    return written * frame_bytes;
#endif
}
